/**
Company: Sarkar software technologys
WebSite: http://www.sarkar.cn
Author: yeganaaa
Date : 8/1/18
Time: 4:04 AM
 */

package com.ug_project.dict.engine.indextable

import java.nio.ByteBuffer
import java.nio.channels.SeekableByteChannel

class IndexTable(val channel: SeekableByteChannel) {
    companion object {

        const val bmapStart = IndexTableMeta.metaSize
        const val bmapSize = 1024 * 1024 * 8 //8MB BMap, supports 1GB find address.
        const val dataBlockStart = bmapStart + bmapSize
    }

    val meta = IndexTableMeta(this)
    private val bytes = ByteArray(1)
    private val buffer = ByteBuffer.wrap(bytes)


    fun load() {
        meta.capacity = bmapSize * 8

        val nothing = allocateBlock()
        AddressBlock(this, nothing, 0).apply {
            addrs.forEachIndexed { index, i ->
                addrs[index] = Int.MAX_VALUE
            }
            writeToChannel()
        }
    }

    init {
        load()
    }

    fun resetChannelPointerToBlockHome(blockId: Int) {
        channel.position(bmapStart + (blockId / 8).toLong())
    }


    private fun writeBlockAllocated(blockId: Int, value: Boolean) {
        var bits = readBlocksAllocation(blockId)
        bits = bits.bit(blockId % 8, value)
        buffer.clear()
        buffer.put(bits)
        buffer.clear()
        resetChannelPointerToBlockHome(blockId)
        channel.write(buffer)
    }

    fun readBlockAllocated(blockId: Int): Boolean {
        val value = readBlocksAllocation(blockId)
        buffer.clear()
        return value.bit(blockId % 8)
    }


    fun allocateBlock(blockAction: (blockId: Int) -> Unit = {}): Int {
        var blockId = -1
        for (i in meta.lastEmptyBlock until bmapSize) {
            val isEmpty = !readBlockAllocated(i)
            if (isEmpty) {
                blockAction(i)
                writeBlockAllocated(i, true)
                meta.lastEmptyBlock = i + 1
                meta.used += 1
                meta.writeToChannel()
                blockId = i
                break
            }
        }
        return blockId
    }

    fun removeBlockAllocation(blockId: Int) {
        writeBlockAllocated(blockId, false)
        if (meta.lastEmptyBlock > blockId) meta.lastEmptyBlock = blockId
        meta.used -= 1
        meta.writeToChannel()
    }

    private fun readBlocksAllocation(blockId: Int): Byte {
        buffer.clear()
        resetChannelPointerToBlockHome(blockId)
        channel.read(buffer)
        buffer.clear()
        return buffer.get()
    }


}